﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Pfz.Extensions;

namespace Pfz.Serialization.BinaryBuiltIn
{
	/// <summary>
	/// Serializer for any [Serializable] class (using reflection).
	/// </summary>
	public sealed class SerializableSerializer<T>:
		ItemSerializer<T>
	{
		/// <summary>
		/// Gets the singleton instance.
		/// </summary>
		public static readonly SerializableSerializer<T> Instance = new SerializableSerializer<T>();

		private static readonly FieldInfo[] _fields;

		/// <summary>
		/// Initializes and validates a serializer for the given generic parameter type.
		/// </summary>
		static SerializableSerializer()
		{
			if (!typeof(T).IsSerializable)
				throw new ArgumentException("Type argument T must be serializable, but " + typeof(T).FullName + " is not. Are you missing a [Serializable] attribute?");

			if (typeof(ISerializable).IsAssignableFrom(typeof(T)))
				throw new ArgumentException("This serializer is not capable of serializing ISerializable objects and type " + typeof(T).FullName + " is ISerializable. Use SerializableInterfaceSerializer instead.");

			var fields = new List<FieldInfo>();
			var type = typeof(T);
			while(type != null)
			{
				var thisLevelFields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
				fields.AddRange(thisLevelFields);
				type = type.BaseType;
			}

			_fields = fields.ToArray();
		}

		private SerializableSerializer()
		{
		}

		/// <summary>
		/// Serializes the serializable item.
		/// </summary>
		public override void Serialize(ConfigurableSerializerBase serializer, T item)
		{
			object[] array = item as object[];
			if (array != null)
			{
				serializer.Stream.WriteCompressedInt32(array.Length);
				var elementType = array.GetType().GetElementType();
				foreach(var arrayItem in array)
					serializer.InnerSerialize(arrayItem, elementType);

				return;
			}

			var values = FormatterServices.GetObjectData(item, _fields);
			int count = values.Length;
			for(int i=0; i<count; i++)
				serializer.InnerSerialize(values[i], _fields[i].FieldType);
		}

		/// <summary>
		/// Deserializes a [Serializable] item.
		/// </summary>
		public override T Deserialize(ConfigurableSerializerBase deserializer)
		{
			if(typeof(T).IsArray)
			{
				int count = deserializer.Stream.ReadCompressedInt32();
				Type elementType = typeof(T).GetElementType();
				object[] array = (object[])Array.CreateInstance(elementType, count);
				for(int i=0; i<count; i++)
					array[i] = deserializer.InnerDeserialize(elementType);

				return (T)(object)array;
			}

			object result = FormatterServices.GetSafeUninitializedObject(typeof(T));
			int count2 = _fields.Length;
			if (count2 == 0)
				return (T)result;

			object[] values = new object[count2];
			for(int i=0; i<count2; i++)
				values[i] = deserializer.InnerDeserialize(_fields[i].FieldType);

			FormatterServices.PopulateObjectMembers(result, _fields, values);
			return (T)result;
		}
	}
}
